(function () {
	if (window.$) {
		var _$ = window.$;
	}
	
	var $ = window.$ = function (id) {
		if (typeof id == 'string') return document.getElementById(id);
		return id;
	};
	
	
	/* DOM utility */
	if (window.DOM) {
		var _DOM = window.DOM;
	}
	
	var DOM = window.DOM = {};
	
	DOM.previous = function (element) {
		do {
			element = element.previousSibling;
		} while (element && element.nodeType != 1);
		return element;
	};
	
	DOM.next = function (element) {
		do {
			element = element.nextSibling;
		} while (element && element.nodeType != 1);
		return element;
	};
	
	DOM.first = function (element) {
		element = element.firstChild;
		return element && element.nodeType != 1 ? DOM.next(element) : element;
	};
	
	DOM.last = function (element) {
		element = element.lastChild;
		return element && element.nodeType != 1 ? DOM.previous(element) : element;
	};
	
	DOM.domReady = function (fn) {
		if (DOM.domReady.done) return fn();
		
		if (DOM.domReady.timer) {
			DOM.domReady.ready.push(fn);
		} else {
			Event.addEvent(window, 'load', _isDOMReady);
			DOM.domReady.ready = [fn];
			
			DOM.domReady.timer = setInterval(_isDOMReady, 13);
		}
	};
	
	function _isDOMReady() {
		if (DOM.domReady.done) return false;
		
		if (document && document.getElementsByTagName && $ && document.body) {
			clearInterval(DOM.domReady.timer);
			DOM.domReady.timer = null;
			
			for (var i = 0; i < DOM.domReady.ready.length; i++) {
				DOM.domReady.ready[i]();
			}
			
			DOM.domReady.ready = null;
			DOM.domReady.done = true;
		}
	}
	
	DOM.hasClass = function(name, type) {
		var result = [];
		var regExp = new RegExp('(^|\\s)' + name + '(\\s|$)');
		var elements = document.getElementsByTagName(type || '*');
		for (var i = 0; i < elements.length; i++) {
			if (regExp.test(elements[i].className)) {
				result.push(elements[i]);
			}
		}
		
		return result;
	};
	
	
	/* Event utility */
	if (window.Event) {
		window._Event = window.Event;
	}
	
	var Event = window.Event = {};
	
	Event.getEvent = function() {
		if (window.event) {
			return window.event;
		}
		var func = getEvent.caller;
		while (func != null) {
			var arg0 = func.arguments[0];
			if (arg0 && (arg0.constructor == Event || arg0.constructor == MouseEvent)) {
				return arg0;
			}			
			func = func.caller;
		}
		return null;
	}
	
	Event.getSource = function(e) {
		return e.target || e.srcElement;
	}
	
	Event.stopBubble = function(e) {
		if (e && e.stopPropagation) {
			e.stopPropagation();
		} else {
			window.event.cancelBubble = true;
		}
	};
	
	Event.stopDefault = function(e) {
		if (e && e.preventDefault) {
			e.preventDefault();
		} else {
			window.event.returnValue = false;
		}
		
		return false;
	};
	
	Event.addEvent = function(element, type, fn) {
		if (element.addEventListener) {
			element.addEventListener(type, fn, false);
		} else if (element.attachEvent) {
			element['e' + type + fn] = fn;
			element[type + fn] = function() {
				element['e' + type + fn](window.event);
			};
			element.attachEvent('on' + type, element[type + fn]);
		}
	}
	
	Event.removeEvent = function(element, type, fn) {
		if (element.removeEventListener) {
			element.removeEventListener(type, fn, false);
		} else if (element.detachEvent) {
			element.detachEvent('on' + type, element[type + fn]);
			element[type + fn] = null;
			element['e' + type + fn] = null;
		}
	}

	Event.getX = function(e) {
		e = e || window.event;

		return e.pageX || e.clientX + document.body.scrollLeft;
	};

	Event.getY = function(e) {
		e = e || window.event;

		return e.pageY || e.clientY + document.body.scrollTop;
	};

	Event.getElementX = function(e) {
		return (e && e.layerX) || window.event.offsetX;
	};

	Event.getElementY = function(e) {
		return (e && e.layerY) || window.event.offsetY;
	};
	

	/* CSS utility */
	if (window.CSS) {
		window._CSS = winow.CSS;
	}				
	
	var CSS = window.CSS = {};
	
	CSS.getStyle = function(element, name) {
		if (element.style[name]) {
			return element.style[name];
		} else if (element.currentStyle) {
			return element.currentStyle[name];
		} else if (document.defaultView && document.defaultView.getComputedStyle) {
			name = name.replace(/([A-Z])/g, '-$1');
			name = name.toLowerCase();
			
			var s = document.defaultView.getComputedStyle(element, '');
			return s && s.getPropertyValue(name);
		} else {
			return null;
		}
	};
	
	CSS.pageX = function(element) {
		return element.offsetParent ? element.offsetLeft + CSS.pageX(element.offsetParent) : element.offsetLeft;
	};
	
	CSS.pageY = function(element) {
		return element.offsetParent ? element.offsetTop + CSS.pageX(element.offsetParent) : element.offsetTop;
	};

	CSS.parentX = function(element) {
		return element.parentNode == element.offsetParent ? element.offsetLeft : CSS.pageX(element) - CSS.pageX(element.parentNode);
	};

	CSS.parentY = function(element) {
		return element.parentNode == element.offsetParent ? element.offsetTop : CSS.pageY(element) - CSS.pageY(element.parentNode);
	};

	CSS.posX = function(element) {
		return parseInt(CSS.getStyle(element, 'left'));
	};

	CSS.posY = function(element) {
		return parseInt(CSS.getStyle(element, 'top'));
	};

	CSS.setX = function(element, position) {
		element.style.left = position + 'px';
	};

	CSS.setY = function(element, position) {
		element.style.top = position + 'px';
	};

	CSS.addX = function(element, position) {
		CSS.setX(element, CSS.posX(element) + position);
	};

	CSS.addY = function(element, position) {
		CSS.setY(element, CSS.posY(element) + position);
	};

	CSS.getHeight = function(element) {
		return parseInt(CSS.getStyle(element, 'height'));
	};

	CSS.getWidth = function(element) {
		return parseInt(CSS.getStyle(element, 'weight'));
	};

	CSS.fullHeight = function(element) {
		if (CSS.getStyle(element, 'display') != 'none') {
			return element.offsetHeight || CSS.getHeight(element);
		}

		var old = _resetCSS(element, {
			display: 'block',
			visibility: 'hidden',
			position: 'absolute'
		});
		
		var h = element.clientHeight || CSS.getHeight(element);

		_restoreCSS(element, old);

		return h;
	};

	CSS.fullWidth = function(element) {
		if (CSS.getStyle(element, 'display') != 'none') {
			return element.offsetWidth || CSS.getWidth(element);
		}

		var old = _resetCSS(element, {
			display: 'block',
			visibility: 'hidden',
			position: 'absolute'
		});

		var w = element.clientWidth || CSS.getWidth(element);

		_restoreCSS(element, old);

		return w;
	};

	function _resetCSS(element, prop) {
		var old = {};

		for (var i in prop) {
			old[i] = element.style[i];

			element.style[i] = prop[i];
		}

		return old;
	}

	function _restoreCSS(element, prop) {
		for (var i in prop) {
			element.style[i] = prop[i];
		}
	}

	CSS.pageHeight = function() {
		return document.body.scrollHeight;
	};

	CSS.pageWidth = function() {
		return document.body.scrollWidth;
	};

	CSS.scrollX = function() {
		var de = document.documentElement;

		return self.pageXOffset || (de && de.scrollLeft) || document.body.scrollLeft;
	};

	CSS.scrollY = function() {
		var de = document.documentElement;

		return self.pageYOffset || (de && de.scrollTop) || document.body.scrollTop;
	};

	CSS.windowHeight = function() {
		var de = document.documentElement;

		return self.innerHeight || (de && de.clientHight) || document.body.clientHeight;
	};

	CSS.windowWidth = function() {
		var de = document.documentElement;

		return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
	};

	CSS.hide = function(element) {
		var currentDisplay = CSS.getStyle(element, 'display');

		if (currentDisplay != 'none') {
			element.$oldDisplay = currentDisplay;
		}

		element.style.display = 'none';
	};

	CSS.show = function(element) {
		element.style.display = element.$oldDisplay || 'block';
	};

	CSS.setOpacity = function(element, level) {
		if (element.filters) {
			element.style.filter = 'alpha(opacity=' + level + ')';
		} else {
			element.style.opacity = level / 100;
		}
	};

	CSS.fadeIn = function(element) {
		CSS.setOpacity(element, 0);

		CSS.show(element);

		for (var i = 0; i <= 100; i += 5) {
			(function(){
				var position = i;
				
				setTimeout(function() {
					CSS.setOpacity(element, position);
				}, (position + 1) * 10);
			})();
		}
	};
	
	
	/* Form utility */
	if (window.Form) {
		window._Form = window.Form;
	}
	
	var Form = window.Form = {};
	
	Form.checkRequired = function(element) {
		if (element.type == 'checkbox' || element.type == 'radio') {
			return _getInputsByName(element.name).numChecked;
		} else {
			return element.value.length > 0 && element.value != element.defaultValue;
		}
	};
	
	function _getInputsByName(name) {
		var results = [];
		
		results.numChecked = 0;
		
		var input = document.getElementsByTagName('input');
		for (var i = 0; i < input.length; i++) {
			if (input[i].name == name) {
				results.push(input[i]);
				if (input[i].checked) {
					results.numChecked++;
				}
			}
		}
		
		return results;
	}
	
	Form.checkEmail = function(element) {
		return element.value == '' || /^[a-z0-9_+.-]+\@([a-z0-9-]+\.)+[a-z0-9]{2,4}$/i.test(element.value);
	};
	
	Form.checkUrl = function(element) {
		return element.value == '' || /^https?:\/\/([a-z0-9-]+\.)+[a-z0-9]{2,4}.*$/.test(element.value);
	};
	
	Form.checkDate = function(element) {
		return !element.value || /\d{2,4}\-\d{1,2}\-\d{1,2}/.test(element.value);
	};
	
	var _errorMessage = {
		required: {
			message: 'This field is required.',
			test: function(element, load) {
				return Form.checkRequired(element) || load;
			}
		},
		
		email: {
			message: 'Not a valid email address.',
			test: function(element) {
				return Form.checkEmail(element);
			}
		},
		
		date: {
			message: 'Not a valid date.',
			test: function(element) {
				return Form.checkDate(element);
			}
		},
		
		url: {
			message: 'Not a valid URL.',
			test: function(element) {
				return Form.checkUrl(element);
			}
		}
	};
	
	Form.validateForm = function(form, load) {
		var valid = true;
		
		for (var i = 0; i < form.elements.length; i++) {						
			if (!Form.validateField(form.elements[i], load)) {
				valid = false;
			}
		}
		
		return valid;
	};
	
	Form.validateField = function(element, load) {
		_hideErrors(element);
		
		var errors = [];
		
		for (var name in _errorMessage) {
			var regExp = new RegExp('(^|\\s)' + name + '(\\s|$)');
			
			if (regExp.test(element.className) && !_errorMessage[name].test(element, load)) {
				errors.push(_errorMessage[name].message);
			}
		}
		
		if (errors.length) {
			_showErrors(element, errors);
		}
		
		return errors.length == 0;
	};
	
	function _hideErrors(element) {
		var next = element.nextSibling;
		
		if (next && next.nodeName == 'UL' && next.className == 'errors') {
			element.parentNode.removeChild(next);
		}
	}
	
	function _showErrors(element, errors) {
		var next = element.nextSibling;
		
		if (next && (next.nodeName != 'UL' || next.className != 'errors')) {
			next = document.createElement('ul');
			next.className = 'errors';
			element.parentNode.insertBefore(next, element.nextSibling);
		}
		
		for (var i = 0; i < errors.length; i++) {
			var li = document.createElement('li');
			li.innerHTML = errors[i];
			
			next.appendChild(li);
		}
	}
	
	Form.focusOnFirst = function(obj) {
		var form = obj || document.forms[0];
		if (!form) return;
		for (var i = 0; i < form.elements.length; i++) {
			var field = form.elements[i];
			if (field.type && field.type != 'hidden') {
				field.focus();
				return;
			}
		}
	};
})();